<?php

declare(strict_types=1);

namespace DreamCat\ControllerParamGen\Cases;

use DreamCat\Array2Class\Array2ClassConverter;
use DreamCat\Array2Class\Exception\ConvertFailed;
use DreamCat\ControllerParamGen\ControllerParamGeneratorInterface;
use DreamCat\ControllerParamGen\Exception\ParamGenerateFailed;
use DreamCat\ControllerParamGen\Generator\ControllerParamGenerator;
use DreamCat\ControllerParamGen\HelperClass\Container\DemoContainer;
use DreamCat\ControllerParamGen\HelperClass\Controller\DemoController;
use DreamCat\ControllerParamGen\HelperClass\Factory\ServerRequestFactory;
use DreamCat\ControllerParamGen\HelperClass\Vo\ComplexVO;
use DreamCat\ControllerParamGen\HelperClass\Vo\DemoVo;
use DreamCat\ControllerParamGen\ParamCreateInterface;
use DreamCat\ObjectValid\Validor\Validator;
use Dreamcat\PropertyAnalysis\Pojo\BuildinType;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionException;
use ReflectionMethod;
use RuntimeException;
use stdClass;

/**
 * 单元测试
 * @author vijay
 */
class CtlParamGenTest extends TestCase
{
    /**
     * 测试控制器方法的参数解析
     * @param string $fnName 控制器方法
     * @param array $pathVariables 占位符列表
     * @param ServerRequestInterface $request 请求数据
     * @param array $expect 期望的参数列表
     * @return void
     * @throws ReflectionException
     * @dataProvider paramParseData
     */
    public function testParamParse(string $fnName, array $pathVariables, ServerRequestInterface $request, array $expect)
    {
        $ref = new ReflectionMethod(DemoController::class, $fnName);
        $result = $this->getActionParamFactory()->generateParams($ref, $request, $pathVariables);
        static::assertEquals($expect, $result, "方法{$fnName}参数解析不正确");
    }

    /**
     * 生成控制器方法参数生成器
     * @return ControllerParamGeneratorInterface 控制器方法参数生成器
     */
    private function getActionParamFactory(): ControllerParamGeneratorInterface
    {
        return new ControllerParamGenerator(new DemoContainer(), new Array2ClassConverter());
    }

    /**
     * 测试复杂vo无法赋值的情况
     * @return void
     */
    public function testComplexVO()
    {
        $this->expectExceptionMessage("无法构建 " . DemoController::class . "::testComplexVo::vo 参数");
        $this->expectException(RuntimeException::class);
        $ref = new ReflectionMethod(DemoController::class, "testComplexVo");
        $this->getActionParamFactory()->generateParams($ref, ServerRequestFactory::createByPostJson("", ""), []);
    }

    /**
     * 测试无法解析请求体的问题
     * @return void
     */
    public function testCantParseBody()
    {
        $contentType = "application/unknown";
        $this->expectExceptionMessage("无法解析消息体, type = {$contentType}");
        $this->expectException(RuntimeException::class);
        $ref = new ReflectionMethod(DemoController::class, "testDemoVo");
        $request = ServerRequestFactory::createByPostJson(
            "",
            [
                "ab",
                "cc",
            ]
        );
        $request = $request->withHeader("Content-Type", $contentType);
        $this->getActionParamFactory()->generateParams($ref, $request, []);
    }

    /**
     * 测试参数类型为callable的情况
     * @return void
     */
    public function testParamCallback()
    {
        $ref = new ReflectionMethod(DemoController::class, "testBodyCall");
        $str = uniqid("call-");
        $request = ServerRequestFactory::createByPostJson("", ["c" => $str]);
        $result = $this->getActionParamFactory()->generateParams($ref, $request, []);
        static::assertCount(1, $result, "参数个数不正确");
        static::assertTrue(is_callable($result[0]), "参数类型不正确");
        static::assertTrue($str === $result[0](), "参数返回不正确");
    }

    /**
     * 测试参数解析的数据供给器
     * @return array
     */
    public function paramParseData(): array
    {
        return [
            [
                "demo",
                ["label" => "ruiming"],
                ServerRequestFactory::createByPostJson("", ["htes" => 5], ["page" => 5])
                    ->withAddedHeader("headerV", "token")
                    ->withAddedHeader(
                        "hList",
                        [
                            "aa",
                            "cd",
                            "bb",
                        ]
                    )
                    ->withCookieParams(
                        [
                            "u" => 32,
                            "f" => 55,
                        ]
                    ),
                [
                    "ruiming",
                    5,
                    ["htes" => 5],
                    ["label" => "ruiming"],
                    10,
                    "token",
                    32,
                    [
                        "aa",
                        "cd",
                        "bb",
                    ],
                ],
            ],
            [
                "index",
                ["autoPath" => "value1"],
                ServerRequestFactory::createByPostJson(
                    "",
                    ["content" => "json"],
                    [
                        "p" => "pp",
                        "p2" => 3,
                    ]
                ),
                [
                    "pp",
                    3,
                    "{\"content\":\"json\"}",
                    "value1",
                    [
                        "p" => "pp",
                        "p2" => 3,
                    ],
                ],
            ],
            [
                "testPost",
                [],
                ServerRequestFactory::createByPostForm(
                    "",
                    ["field" => "form1"],
                    [],
                    "test.domain.com",
                    false
                ),
                [
                    "form1",
                    null,
                    0,
                    "",
                ],
            ],
            [
                "testPost",
                [],
                ServerRequestFactory::createByPostForm(
                    "",
                    ["field" => "form1"]
                ),
                [
                    "form1",
                    null,
                    0,
                    "",
                ],
            ],
            [
                "testPost",
                [],
                ServerRequestFactory::createByPostJson(
                    "",
                    ["field" => "form2"]
                ),
                [
                    "form2",
                    null,
                    0,
                    "",
                ],
            ],
            [
                "testBodyField",
                [],
                ServerRequestFactory::createByPostJson(
                    "",
                    [
                        "s" => "f",
                        "i" => "5",
                        "f" => "3.2",
                        "b" => "1",
                        "a" => ["val"],
                    ]
                ),
                [
                    "f",
                    5,
                    3.2,
                    true,
                    ["val"],
                ],
            ],
            [
                "testDemoVo",
                [],
                ServerRequestFactory::createByPostJson(
                    "",
                    ["page" => "3"]
                ),
                [new DemoVo(3)],
            ],
            [
                "testStd",
                ["size" => 3],
                ServerRequestFactory::createByPostJson("", ["htes" => 5], ["page" => 5]),
                [
                    (object)["htes" => 5],
                    new DemoVo(5),
                    new DemoVo(1, 3),
                ],
            ],
            [
                "testNotStdClass",
                [],
                ServerRequestFactory::createByPostJson(
                    "",
                    ""
                ),
                [new stdClass()],
            ],
            [
                "noTip",
                ["no" => 5],
                ServerRequestFactory::createByPostJson("", ""),
                [5],
            ],
            [
                "oneGet",
                [],
                ServerRequestFactory::createByPostJson("", ""),
                [null],
            ],
            [
                "unionType",
                [],
                ServerRequestFactory::createByPostJson("", ""),
                [""],
            ],
            [
                "protocol",
                [],
                ServerRequestFactory::createByPostJson("", "", ["key" => ["mmc"]]),
                ["default"],
            ],
        ];
    }

    /**
     * @param string $fnName 控制器方法
     * @param array $pathVariables 占位符列表
     * @param ServerRequestInterface $request 请求数据
     * @param string $errorClass 异常类
     * @param string $errorMsg 异常信息
     * @param int $errorCode 异常码
     * @return void
     * @throws ReflectionException
     * @dataProvider paramParseErrorData
     */
    public function testParamParseError(
        string $fnName,
        array $pathVariables,
        ServerRequestInterface $request,
        string $errorClass,
        string $errorMsg,
        int $errorCode
    ) {
        $this->expectException($errorClass);
        $this->expectExceptionMessage($errorMsg);
        $this->expectExceptionCode($errorCode);
        $ref = new ReflectionMethod(DemoController::class, $fnName);
        $this->getActionParamFactory()->generateParams($ref, $request, $pathVariables);
    }

    /**
     * 解析异常的数据供给器
     * @return array 测试数据
     */
    public function paramParseErrorData(): array
    {
        return [
            [
                "testPost",
                [],
                ServerRequestFactory::createByPostJson("", "{\"content:\"json\"}"),
                RuntimeException::class,
                "json decode error: Syntax error",
                4,
            ],
        ];
    }

    /**
     * 测试自定义构建方法的
     * @return void
     * @throws ReflectionException
     */
    public function testCustomSpec()
    {
        /** @var ControllerParamGenerator $generator */
        $generator = $this->getActionParamFactory();
        $expect = uniqid();
        $o = new class($expect) implements ParamCreateInterface {
            /**
             * @param $str
             */
            public function __construct(private $str)
            {
            }

            /**
             * @inheritDoc
             */
            public function createParam(ServerRequestInterface $serverRequest, string $type): mixed
            {
                return $this->str;
            }
        };
        $generator->addSpecParam(BuildinType::STRING, $o);
        $list = $generator->getSpecParams();
        self::assertCount(1, $list);
        self::assertEquals(spl_object_id($o), spl_object_id($list[BuildinType::STRING]));
        $fnName = "unionType";
        $pathVariables = [];
        $request = ServerRequestFactory::createByPostJson("", "");
        $result = $generator->generateParams(
            new ReflectionMethod(DemoController::class, $fnName),
            $request,
            $pathVariables
        );
        static::assertEquals([$expect], $result, "方法{$fnName}参数解析不正确");
    }

    /**
     * 测试自定义容器的情况
     * @return void
     * @throws ReflectionException
     */
    public function testCustomContainer()
    {
        /** @var ControllerParamGenerator $generator */
        $generator = $this->getActionParamFactory();
        $container = new DemoContainer();
        $o = new stdClass();
        $o->kk = uniqid();
        $container->add(stdClass::class, $o);
        $container->add(ComplexVO::class, null);
        $generator->setContainer($container);
        self::assertEquals(spl_object_id($container), spl_object_id($generator->getContainer()));

        $fnName = "customStd";
        $pathVariables = [];
        $request = ServerRequestFactory::createByPostJson("", "");
        $result = $generator->generateParams(
            new ReflectionMethod(DemoController::class, $fnName),
            $request,
            $pathVariables
        );
        static::assertEquals([$o], $result, "方法{$fnName}参数解析不正确");

        $fnName = "testComplexVo";
        $request = ServerRequestFactory::createByPostJson("", "");
        try {
            $generator->generateParams(new ReflectionMethod(DemoController::class, $fnName), $request, []);
            $hasError = false;
        } catch (ParamGenerateFailed $exception) {
            self::assertEquals(ParamGenerateFailed::class, get_class($exception));
            self::assertEquals("testComplexVo::vo 参数不为能null", $exception->getMessage());
            $hasError = true;
        }
        self::assertTrue($hasError);
    }

    /**
     * 测试set方法
     * @return void
     */
    public function testSetter()
    {
        /** @var ControllerParamGenerator $generator */
        $generator = $this->getActionParamFactory();
        $validor = new Validator();
        $generator->setValidation($validor);
        self::assertEquals(spl_object_id($validor), spl_object_id($generator->getValidation()));
    }

    /**
     * 测试转换失败的情况
     * @return void
     * @throws ReflectionException
     */
    public function testConvertFailed()
    {
        $generator = $this->getActionParamFactory();
        $fnName = "testNotStdClass";
        $request = ServerRequestFactory::createByPostJson("", ["c" => uniqid("dd-")]);
        try {
            $generator->generateParams(new ReflectionMethod(DemoController::class, $fnName), $request, []);
            $hasError = false;
        } catch (ParamGenerateFailed $exception) {
            self::assertEquals(ParamGenerateFailed::class, get_class($exception));
            self::assertEquals("stdClass无法使用非数组构建", $exception->getMessage());
            $previous = $exception->getPrevious();
            self::assertTrue($previous instanceof ConvertFailed);
            self::assertEquals($exception->getMessage(), $previous->getMessage());
            $hasError = true;
        }
        self::assertTrue($hasError);
    }
}

# end of file
